package sfs

import (
	"context"
	"crypto/sha1"
	"github.com/chicken-team-outside/chicken_transmission/api_messages"
	"github.com/chicken-team-outside/chicken_transmission/driver"
	"github.com/chicken-team-outside/chicken_transmission/utils"
	"github.com/golang/protobuf/proto"
	"go.uber.org/zap"
	"io"
	"os"
	"slices"
)

type ReaderHandler struct {
	base     *api_messages.ReadFileReply
	client   *FsClient
	size     int64
	offset   int64
	isDirect bool
}

/*
OpenReader 打开文件读取流

	ctx: 控制读取
	offset: 读取开始偏移量
*/
func (h *ReaderHandler) OpenReader(ctx context.Context, offset, end int64) (io.ReadCloser, error) {
	if h.isDirect {
		return utils.NewBytesReadCloser(h.base.Data, int(offset)), nil
	} else {
		return h.client.downloadBlock(ctx, offset, end, h.base.Link)
	}
}

// Size 获取数据大小
func (h *ReaderHandler) Size() int64 {
	return h.size
}

/*
ReadFile 从目标设备指定文件路径读取数据，操作成功返回文件读取操作对象 ReaderHandler

	ctx: 控制读取
	clientId: 目标设备ID
	path: 需要读取的文件路径
	offset: 读取开始位置
	size: 需要读取的数据长度
	sha1CheckSum: 当此参数长度等于 sha1.Size时,用于校验数据源内容的SHA-1校验值是否与之匹配(可选参数)
*/
func (p *FsClient) ReadFile(ctx context.Context, clientId uint64, path string, offset int64, size int64, sha1CheckSum []byte) (*ReaderHandler, error) {
	timeout := p.timeout.Load()
	isDirect := p.directSendMaxSize.Load() > size
	handler := NewOperatorKeeperClient(ctx, p, timeout)
	defer handler.Close()
	rsp, err := handler.QueryAndWait(clientId, api_messages.MessageType_ReadFile, &api_messages.ReadFileQuery{
		Timeout: int32(timeout),
		Size:    size,
		Offset:  offset,
		Path:    path,
		Direct:  isDirect,
		Sha1:    sha1CheckSum,
	})
	if err != nil {
		return nil, err
	}
	link := new(api_messages.ReadFileReply)
	err = proto.Unmarshal(rsp, link)
	if err == nil {

	}
	return &ReaderHandler{
		base:     link,
		client:   p,
		isDirect: isDirect,
		size:     size,
		offset:   offset,
	}, err
}

func (p *FsClient) readFileProvider(ctx context.Context, header *api_messages.MessageHeader, data []byte) {
	if ctx.Err() == nil {
		query := new(api_messages.ReadFileQuery)
		err := proto.Unmarshal(data, query)
		if err != nil {
			return
		}
		keeper := NewOperatorKeeperServer(context.Background(), header.ClientId, header.Id, p, query.Timeout)
		defer keeper.Close()
		go keeper.keepAlive()
		var localDriver driver.LocalDriver
		if err == nil {
			localDriver = p.GetLocal()
			if localDriver == nil {
				err = ErrNotSetLocalDriver
			}
		}
		var file driver.LocalDriverFile
		if err == nil {
			file, err = localDriver.OpenFile(query.Path, os.O_RDONLY, 0666)
		}
		if err == nil {
			defer func() {
				if file != nil {
					file.Close()
				}
			}()
			_, err = file.Seek(query.Offset, io.SeekStart)
		}
		result := new(api_messages.ReadFileReply)
		if err == nil {
			if query.Direct {
				result.Data = make([]byte, query.Size)
				_, err = io.ReadFull(file, result.Data)
				if err == nil {
					m := sha1.New()
					m.Write(result.Data)
					result.Sha1 = m.Sum(result.Sha1)
					if len(query.Sha1) == sha1.Size && !slices.Equal(query.Sha1, result.Sha1) {
						err = ErrChecksumNotEqual
					}
				}
			} else {
				result.Link, result.Sha1, err = p.uploadBlock(keeper.ctx, utils.NewSubReaderSeeker(file, query.Offset, int64(query.Size)), int64(query.Size), query.Sha1)
			}
		}
		if err != nil {
			err = keeper.sendError(err)
		} else {
			err = keeper.success(result)
		}
		if err != nil {
			zap.L().Warn("failed to send success message to client", zap.Error(err), zap.Uint64("client_id", header.ClientId), zap.Uint64("replay_id", header.Id))
		}
	}
}
